Переменные
Переменные используются для хранения значений (sic!). Переменная характеризуется типом и именем. Начнём с имени. В си переменная может начинаться с подчерка или буквы, но не с числа. Переменна может включать в себя символы английского алфавита, цифры и знак подчёркивания. Переменная не должна совпадать с ключевыми словами (это специальные слова, которые используются в качестве управляющих конструкций, для определения типов и т.п.)
auto | double | int | struct |
break | else | long | switch |
register | typedef | char | extern |
return | void | case | float |
unsigned | default | for | signed |
union | do | if | sizeof |
volatile | continue | enum | short |
while | inline |
А также ряд других слов, специфичных для данной версии компилятора, например far, near, tiny, huge, asm, asm и пр.
Например, правильные идентификаторы
a
, _
, _1_
, Sarkasm
, a_long_variable
, aLongVariable
, var19
, defaultX
, char_type
неверные
1a
, $value
, a-long-value
, short
Си - регистрозависимый язык. Переменные с именами a и A, или end и END, или perfectDark и PerfectDarK – это различные переменные.
Типы переменных
Тип переменной определяет
- Размер переменной в байтах (сколько байт памяти выделит компьютер для хранения значения)
- Представление переменной в памяти (как в двоичном виде будут расположены биты в выделенной области памяти).
В си несколько основных типов. Разделим их на две группы - целые и числа с плавающей точкой.
Целые
char
- размер 1 байт. Всегда! Это нужно запомнить.short
- размер 2 байтаint
- размер 4 байтаlong
- размер 4 байтаlong long
- размер 8 байт.
Здесь следует сделать замечание. Размер переменных в си не определён явно, как размер в байтах. В стандарте только указано, что
char <= short <= int <= long <= long long
Указанные выше значения характерны для компилятора VC2012 на 32-разрядной машине. Так что, если ваша программа зависит от размера переменной, не поленитесь узнать её размер.
Теперь давайте определим максимальное и минимальное число, которое может хранить переменная каждого из типов. Числа могут быть как положительными, так и отрицательными. Отрицательные числа используют один бит для хранения знака. Иногда знак необходим (например, храним счёт в банке, температуру, координату и т.д.), а иногда в нём нет необходимости (вес, размер массива, возраст человека и т.д.). Для этого в си используется модификатор типа signed
и unsigned
.
unsigned char
- все 8 бит под число, итого имеем набор чисел от 00000000 до 11111111 в двоичном виде, то есть от 0 до 255
signed char
от -128 до 128.
В си переменные по умолчанию со знаком. Поэтому запись char и signed char эквивалентны.
Тип | Размер, байт | Минимальное значение | Максимальное значение |
---|---|---|---|
unsigned char | 1 | 0 | 255 |
signed char ( char ) |
1 | -128 | 127 |
unsigned short | 2 | 0 | 65535 |
signed short ( short ) |
2 | -32768 | 32767 |
unsigned int ( unsigned ) |
4 | 0 | 4294967296 |
signed int ( int ) |
4 | -2147483648 | 2147483647 |
unsigned long | 4 | 0 | 4294967296 |
signed long ( long ) |
4 | -2147483648 | 2147483647 |
unsigned long long | 8 | 0 | 18446744073709551615 |
signed long long ( long long ) |
8 | -9223372036854775808 | 9223372036854775807 |
sizeof
В си есть оператор, который позволяет получить размер переменной в байтах.
sizeof переменная
, или sizeof(переменная)
или sizeof(тип)
. Это именно оператор, потому что функция не имеет возможности получить информацию о размере типов
во время выполнения приложения.
Напишем небольшую программу чтобы удостовериться в размерах переменных.
#include <conio.h>
#include <stdio.h>
int main() {
char c;
short s;
int i;
long l;
long long L;
//Вызов sizeof как "функции"
printf("sizeof(char) = %d\n", sizeof(c));
printf("sizeof(short) = %d\n", sizeof(s));
printf("sizeof(int) = %d\n", sizeof(i));
printf("sizeof(long) = %d\n", sizeof(l));
printf("sizeof(long long) = %d\n", sizeof(L));
//Вызов как оператора
printf("sizeof(char) = %d\n", sizeof c);
printf("sizeof(short) = %d\n", sizeof s);
printf("sizeof(int) = %d\n", sizeof i);
printf("sizeof(long) = %d\n", sizeof l);
printf("sizeof(long long) = %d\n", sizeof L);
_getch();
}
(Я думаю ясно, что переменные могут иметь любое валидное имя). Эту программу можно было написать и проще
#include <conio.h>
#include <stdio.h>
int main() {
printf("sizeof(char) = %d\n", sizeof(char));
printf("sizeof(short) = %d\n", sizeof(short));
printf("sizeof(int) = %d\n", sizeof(int));
printf("sizeof(long) = %d\n", sizeof(long));
printf("sizeof(long long) = %d\n", sizeof(long long));
//нельзя произвести вызов sizeof как оператора для имени типа
//sizeof int - ошибка компиляции
_getch();
}
В си один и тот же тип может иметь несколько названий
short === short int
long === long int
long long === long long int
unsigned int === unsigned
Типы с плавающей точкой
float
- 4 байтаlong float
- 8 байтdouble
- 8 байтlong double
- 8 байт
Здесь также приведены значения для VC2012 (x86), по стандарту размер типов
float <= long float <= double <= long double
все числа с плавающей точкой - со знаком.
Тип | Размер, байт | Количество значащих знаков мантиссы | Минимальное значение | Максимальное значение |
---|---|---|---|---|
float | 4 | 6-7 | 1.175494351 E – 38 | 3.402823466 E + 38 |
double | 8 | 15-16 | 2.2250738585072014 E – 308 | 1.7976931348623158 E + 308 |
Переполнение переменных
Си не следит за переполнением переменных. Это значит, что постоянно увеличивая значение, скажем, переменной типа int в конце концов мы "сбросим значение"
#include <conio.h>
#include <stdio.h>
void main() {
unsigned a = 4294967295;
int b = 2147483647;
//Переполнение беззнакового типа
printf("%u\n", a);
a += 1;
printf("%u", a);
//Переполнение знакового типа
printf("%d\n", b);
b += 1;
printf("%d", b);
getch();
}
Вообще, поведение при переполнении переменной определено только для типа unsigned
: беззнаковое целое сбросит значение.
Для остальных типов может произойти что угодно, и если вам необходимо следить за переполнением, делайте это вручную, проверяя аргументы, либо используйте иные способы, зависящие от компилятора и архитектуры процессора.
Постфиксное обозначение типа
При работе с числами можно с помощью литер в конце числа явно указывать его тип, например
- 11 - число типа int
- 10u - unsigned
- 22l или 22L - long
- 3890ll или 3890LL - long long (а также lL или Ll)
- 80.0f или 80.f или 80.0F - float (обязательно наличие десятичной точки в записи)
- 3.0 - число типа double
Экспоненциальная форма записи также по умолчанию обозначает число типа double.
#include <conio.h>
#include <stdio.h>
int main() {
printf("sizeof(int) = %d\n", sizeof(10));
printf("sizeof(unigned) = %d\n", sizeof(10u));
printf("sizeof(long) = %d\n", sizeof(10l));
printf("sizeof(long long) = %d\n", sizeof(10ll));
printf("sizeof(float) = %d\n", sizeof(10.f));
printf("sizeof(double) = %d\n", sizeof(10.));
printf("sizeof(double) = %d\n", sizeof(10e2));
_getch();
}
Следующий код, однако, не будет приводить к ошибкам, потому что происходит неявное преобразование типа
int a = 10u;
double g = 3.f;
Шестнадцатеричный и восьмеричный формат
Во время работы с числами можно использовать шестнадцатеричный и восьмеричный формат представления. Числа в шестнадцатиричной системе счисления начинаются с 0x, в восьмеричной системе с нуля. Соответственное, если число начинается с нуля, то в нём не должно быть цифр выше 7:
#include <conio.h>
#include <stdio.h>
void main() {
int x = 0xFF;
int y = 077;
printf("hex x = %x\n", x);
printf("dec x = %d\n", x);
printf("oct x = %o\n", x);
printf("oct y = %o\n", y);
printf("dec y = %d\n", y);
printf("hex y = %x", y);
_getch();
}
Экспоненциальная форма представления чисел
Экспоненциальной формой представления числа называют представление числа в виде
, где M - мантиса числа, p - степень десяти. При этом у мантисы должен быть один ненулевой знак перед десятичной запятой.
Например 1.25 === 1.25e0
, 123.5 === 1.235e2
, 0.0002341 === 2.341e-4
и т.д.
Представления 3.2435e7
эквивалентно 3.2435e+7
Существеут и другое представление ("инженерное"), в котором степень должна быть кратной тройке.
Например 1.25 === 1.25e0
, 123.5 === 123.5e0
, 0.0002341 === 234.1e-6
, 0.25873256 === 258.73256e-3
и т.д.
Объявление переменных
В си переменные объявляются всегда в начале блока (блок - участок кода ,ограниченный фигурными скобками)
<возвращаемый тип> <имя функции> (<тип> <аргумент> [, <тип> <аргумент>]) {
объявление переменных
всё остальное
}
При объявлении переменной пишется её тип и имя.
int a;
double parameter;
Можно объявить несколько переменных одного типа, разделив имена запятой
long long arg1, arg2, arg3;
Например
#include <stdio.h&>
#include <conio.h>
int main() {
int a = 10;
int b;
while (a>0){
int z = a*a;
b += z;
}
}
Здесь объявлены переменные a и b внутри функции main, и переменная z внутри тела цикла.
Следующий код вызовет ошибку компиляции
int main() {
int i;
i = 10;
int j;
}
Это связано с тем, что объявление переменной стоит после оператора присваивания.
При объявлении переменных можно их сразу инициализировать.
int i = 0;<br/>
При этом инициализация при объявлении переменной не считается за отдельный оператор, поэтому следующий код будет работать
int main() {
int i = 10;
int j;
}
Начальное значение переменной
Очень важно запомнить, что переменные в си не инициализируются по умолчанию нулями, как во многих других языках программирования. После объявления переменной в ней хранится "мусор" - случайное значение, которое осталось в той области памяти, которая была выделена под переменную.
Это связано, в первую очередь, с оптимизацией работы программы: если нет необходимости в инициализации, то незачем тратить ресурсы для записи нулей.
#include <conio.h>
#include <stdio.h>
int main() {
int i;
printf("%d", i);
getch();
}
Если выполнять эту программу на VC, то во время выполнения вылетит предупреждение
Run-Time Check Failure #3 - The variable 'i' is being used without being initialized.
Если нажать "Продолжить", то программа выведет "мусор". В многих других компиляторах при выполнении программы не будет предупреждения.
Область видимости переменной
Переменные бываю локальными (объявленными внутри какой-нибудь функции) и глобальными. Глобальная переменная видна всем функциям, объявленным в данном файле.
Локальная переменная ограничена своей областью видимости. Когда я говорю, что переменная "видна в каком-то месте", это означает, что в этом месте она определена и её можно использовать.
Например, рассмотрим программу, в которой есть глобальная переменная
#include <conio.h>
#include <stdio.h>
int global = 100;
void foo() {
printf("foo: %d\n", global);
}
void bar(int global) {
printf("bar: %d\n", global);
}
int main() {
foo();
bar(333);
getch();
}
Будет выведено
foo: 100
bar: 333
Здесь глобальная переменная global видна всем функциям. Но аргумент функции затирает глобальную переменную, поэтому при передаче аргумента 333 выводится локальное значение 333.
Вот другой пример
#include <conio.h>
#include <stdio.h>
int global = 100;
int main() {
int global = 555;
printf("%d\n", global);
getch();
}
Программа выведет 555. Также, как и в прошлом случае, локальная переменная "важнее".
Переменная, объявленная в некоторой области видимости не видна вне её, например
#include <conio.h>
#include <stdio.h>
int global = 100;
int main() {
int x = 10;
{
int y = 30;
printf("%d", x);
}
printf("%d", y);
}
Этот пример не скомпилируется, потому что переменная y существует только внутри своего блока.
Вот ещё пример, когда переменные, объявленные внутри блока перекрывают друг друга
#include <conio.h>
#include <stdio.h>
int global = 100;
int main() {
int x = 10;
{
int x = 20;
{
int x = 30;
printf("%d\n", x);
}
printf("%d\n", x);
}
printf("%d\n", x);
getch();
}
Программа выведет
30
20
10
Глобальных переменных необходимо избегать. Объяснение почему оставляю на совести вашего опыта.
Переменные могут быть не только целочисленными и с плавающей точкой. Существует множество других типов, которые мы будем изучать в дальнейшем.